#pragma once

#include <iostream>
#include <queue>
#include <cassert>
#include <pthread.h>

const int g_DFLCAP = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(int capacity = g_DFLCAP)
        : _capacity(capacity)
    { 
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Full, nullptr);
        pthread_cond_init(&_Empty, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Full);
        pthread_cond_destroy(&_Empty);
    }

    // 判断阻塞队列是否已满
    bool IsFull()
    {
        return _bq.size() == _capacity;
    }

    // 判断阻塞队列是否为空
    bool IsEmpty()
    {
        return _bq.empty();
    }

    // 向阻塞队列中添加数据
    void push(const T& data)
    {
        pthread_mutex_lock(&_mtx);   // 加锁

        // 轮询检测阻塞队列是否已满
        // 如果满了，则设置等待条件Full
        // 这里使用while而非if是为了避免：(1).函数执行未成功  (2).多线程条件下伪唤醒
        while(IsFull())
        {
            pthread_cond_wait(&_Full, &_mtx);
        }

        _bq.push(data);   // 在队尾添加数据

        // 如果此时阻塞队列中的数据量超过容量一半，就唤醒消费者读数据
        if(_bq.size() > _capacity / 2) pthread_cond_signal(&_Empty);
        pthread_mutex_unlock(&_mtx);
    }

    // 从阻塞队列中提取数据
    void pop(T* pdata)
    {
        // 加锁
        pthread_mutex_lock(&_mtx);

        // 轮询检测阻塞队列是否为空，为空则设置等待_Empty条件
        while(IsEmpty())
        {
            pthread_cond_wait(&_Empty, &_mtx);
        }

        // 读取队头数据，然后删除
        *pdata = _bq.front();
        _bq.pop();

        // 如果此时队列中的数据量不超过一半，那么唤醒生产者发送数据
        if(_bq.size() <= _capacity / 2) pthread_cond_signal(&_Full);
        pthread_mutex_unlock(&_mtx);
    }

private:
    std::queue<T> _bq;
    int _capacity;
    pthread_mutex_t _mtx;
    pthread_cond_t _Full;
    pthread_cond_t _Empty;
};
